package middleware

import (
	"app-box/api_error"
	"app-box/config"
	"bytes"
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"io/ioutil"
	"math"
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

func ParameterSignCheck(ctx *gin.Context) {
	var header struct {
		Sign         string
		Timestamp    int64
		PostmanToken string `header:"Postman-Token"`
	}
	if err := ctx.ShouldBindHeader(&header); err != nil {
		panic(api_error.InvalidParameters(err))
	}
	if config.NotReleaseMode() && len(header.PostmanToken) != 0 { // 开发模式由PostMan发送的请求不校验参数
		return
	}

	if header.Timestamp == 0 || len(header.Sign) == 0 {
		panic(api_error.InvalidParameters(errors.New("Header 中的 Timestamp 和 Sign 为必传参数")))
	}

	var timeNow = time.Now().Unix()
	if math.Abs(float64(header.Timestamp-timeNow)) > 60 {
		panic(api_error.InvalidParameters(errors.New("参数签名校验失败，时间错误")))
	}

	var sign string

	contentType := ctx.GetHeader("Content-Type")
	switch ctx.Request.Method {
	case http.MethodPost, http.MethodPut, http.MethodPatch:
		if len(contentType) != 0 {
			if strings.Contains(contentType, "application/json") {
				sign = getJsonSign(ctx, header.Timestamp)
			} else if strings.Contains(contentType, "multipart/form-data") {
				ctx.Request.ParseMultipartForm(config.System.MaxUploadFileSize)
				sign = getFormSign(ctx, header.Timestamp)
			} else if strings.Contains(contentType, "application/x-www-form-urlencoded") {
				ctx.Request.ParseForm()
				sign = getFormSign(ctx, header.Timestamp)
			} else {
				panic(api_error.InvalidContentTypeError())
			}
		}
	case http.MethodGet, http.MethodDelete:
		ctx.Request.ParseForm()
		sign = getFormSign(ctx, header.Timestamp)
	}

	if sign != header.Sign {
		panic(api_error.InvalidParameters(errors.New("参数签名校验失败")))
	}
}

// 按大写字母的字母表顺序排序
type SortByUperCaseString []string

func (a SortByUperCaseString) Len() int      { return len(a) }
func (a SortByUperCaseString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a SortByUperCaseString) Less(i, j int) bool {
	return strings.ToUpper(a[i]) < strings.ToUpper(a[j])
}

func getFormSign(ctx *gin.Context, Timestamp int64) string {
	keys := make([]string, 0, len(ctx.Request.Form))
	for k := range ctx.Request.Form {
		keys = append(keys, k)
	}
	sort.Sort(SortByUperCaseString(keys))
	strList := make([]string, len(keys))
	for index, key := range keys {
		if values, ok := ctx.Request.Form[key]; ok {
			strList[index] = fmt.Sprintf("%s=%s", key, strings.Join(values, ","))
		}
	}
	str := strings.Join(strList, "&")
	sign := getStrSign(str, Timestamp)
	return sign
}

func getJsonSign(ctx *gin.Context, Timestamp int64) string {
	data, err := ioutil.ReadAll(ctx.Request.Body)
	if err != nil {
		panic(err)
	}
	ctx.Request.Body.Close()                                   // 关闭当前的流
	ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) // 创建新的流供后面的中间件读取
	sign := getStrSign(string(data), Timestamp)
	return sign
}

func getStrSign(str string, Timestamp int64) string {
	strTmp := fmt.Sprintf("%s%d", str, Timestamp)
	strTmp = strings.ToUpper(strTmp)

	shaCtx := sha256.New()
	if _, err := shaCtx.Write([]byte(strTmp)); err != nil {
		panic(err)
	}
	shaStr := fmt.Sprintf("%x", shaCtx.Sum(nil))
	shaStr = strings.ToUpper(shaStr)
	md5Ctx := md5.New()
	if _, err := md5Ctx.Write([]byte(shaStr)); err != nil {
		panic(err)
	}
	return strings.ToUpper(hex.EncodeToString(md5Ctx.Sum(nil)))
}
